﻿using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using CollegeSim.CourseScheduler.Domain.DTO;
using CollegeSim.CourseScheduler.Domain.Interfaces;
using CollegeSim.CourseScheduler.Domain.Interfaces.Repositories;

namespace CollegeSim.CourseScheduler.Persistence
{
    // Author: Ryan Redburn
    // Project Course Scheduler
    // Date: 2/5/2014

    #region [ Updates ]

    // Author: Ryan Redburn
    // Date: 2/22/2014
    // Revision: Introduced exception handling for invalid method arguments

    // Author: Ryan Redburn
    // Date: 2/26/2014
    // Revision: Added exception handling for unique constraints

    // Author: Ryan Redburn
    // Date: 4/6/2014
    // Revision: Fixed logic error in update method and added class comments

    #endregion

    /// <summary>
    /// Repository used to interact with course objects. Provides CRUD and search functionality.
    /// </summary>
    public class CourseRepository : ICourseRepository
    {
        #region [ Methods ]

        /// <summary>
        /// Returns all Courses.
        /// </summary>
        /// <returns>IEnumerable of CourseDto.</returns>
        public IEnumerable<CourseDto> FindAll()
        {
            IEnumerable<CourseDto> courses;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                // Mapping retrieved dbset to targeted return type
                courses = Mapper.Map<IEnumerable<CourseDto>>(context.Courses);
            }

            return courses;
        }

        /// <summary>
        /// Returns all Courses of the given subject.
        /// </summary>
        /// <param name="subject">Three character Course subject.</param>
        /// <returns>IEnumerable of CourseDto.</returns>
        public IEnumerable<CourseDto> FindBySubject(string subject)
        {
            // Throw exception for invalid subject value
            if (String.IsNullOrWhiteSpace(subject))
                throw new ArgumentNullException("subject",
                    "subject does not accept a null or empty string as an argument.");

            IEnumerable<CourseDto> courses;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                // Mapping retrieved dbset to targeted return type
                courses = Mapper.Map<IEnumerable<CourseDto>>(context.Courses.Where(c => c.Subject == subject));
            }

            return courses;
        }

        /// <summary>
        /// Returns all Courses with credits hours matching the given amount.
        /// </summary>
        /// <param name="hours">Number of credits hours.</param>
        /// <returns>IEnumerable of CourseDto.</returns>
        public IEnumerable<CourseDto> FindByCreditHours(int hours)
        {
            // Throw exception for invalid hours value
            if (hours < 1 || hours > 6)
                throw new ArgumentOutOfRangeException("hours",
                    "hours does not accept an integer greater than 6 or less than one as an argument.");

            IEnumerable<CourseDto> courses;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                // Mapping retrieved dbset to targeted return type
                courses = Mapper.Map<IEnumerable<CourseDto>>(context.Courses.Where(c => c.CreditHours == hours));
            }

            return courses;
        }

        /// <summary>
        /// Returns all Courses with the given style, online or not.
        /// </summary>
        /// <param name="isOnline">Course style.</param>
        /// <returns>IEnumerable of CourseDto.</returns>
        public IEnumerable<CourseDto> FindByStyle(bool isOnline)
        {
            IEnumerable<CourseDto> courses;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                // Mapping retrieved dbset to targeted return type
                courses = Mapper.Map<IEnumerable<CourseDto>>(context.Courses.Where(c => c.IsOnline == isOnline));
            }

            return courses;
        }

        /// <summary>
        /// Returns all Courses taught by the given instructor.
        /// </summary>
        /// <param name="instructorId">The instructors integer id.</param>
        /// <returns>IEnumerable of CourseDto.</returns>
        public IEnumerable<CourseDto> FindByInstructor(int instructorId)
        {
            // Throw exception for invalid instructorId value
            if (instructorId >= 0)
                throw new ArgumentOutOfRangeException("instructorId",
                    "instructorId does not accept an interger less than zero as an argument.");

            IEnumerable<CourseDto> courses;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                // Mapping retrieved dbset to targeted return type
                courses = Mapper.Map<IEnumerable<CourseDto>>(context.Courses
                    .Where(c => c.InstructorId == instructorId));
            }

            return courses;
        }

        /// <summary>
        /// Returns all courses taught by the given instructer.
        /// </summary>
        /// <param name="firstName">Instructor first name.</param>
        /// <param name="lastName">Instructor last name.</param>
        /// <returns>IEnumerable of CourseDto.</returns>
        public IEnumerable<CourseDto> FindByInstructor(string firstName, string lastName)
        {
            // Throw exception for invalid firstName value
            if (String.IsNullOrWhiteSpace(firstName))
                throw new ArgumentNullException("firstName",
                    "firstName does not accept a null or empty string as an argument.");

            // Throw exception for invalid lastName value
            if (String.IsNullOrWhiteSpace(lastName))
                throw new ArgumentNullException("lastName",
                    "lastName does not accept a null or empty string as an argument.");

            IEnumerable<CourseDto> courses;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                // Mapping retrieved dbset to targeted return type
                courses = Mapper.Map<IEnumerable<CourseDto>>(context.Courses.
                    Where(c => c.Instructor.FirstName == firstName && c.Instructor.LastName == lastName));
            }

            return courses;
        }

        /// <summary>
        /// Returns courses with the given CRN(course reference number).
        /// </summary>
        /// <param name="crn">Three character course reference number.</param>
        /// <returns>CourseDto.</returns>
        public IEnumerable<CourseDto> FindByCRN(string crn)
        {
            // Throw exception for invalid crn value
            if (String.IsNullOrWhiteSpace(crn))
                throw new ArgumentNullException("crn",
                    "crn does not accept a null or empty string as an argument.");

            IEnumerable<CourseDto> courses;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                // Mapping retrieved dbset to targeted return type
                courses = Mapper.Map<IEnumerable<CourseDto>>(context.Courses.Where(c => c.CRN == crn));
            }

            return courses;
        }

        /// <summary>
        /// Returns a course with the given id.
        /// </summary>
        /// <param name="id">Interger course id</param>
        /// <returns>CourseDto</returns>
        public CourseDto FindById(int id)
        {
            // Throws exception for invalid id value
            if (id < 1)
                throw new ArgumentOutOfRangeException("id",
                    "id does not accept an integer value less than one as an argument.");

            CourseDto course;

            // Open entity connnection
            using (var context = new CollegeSimEntities())
            {
                // Mapping retrieved entity to targeted return type
                course = Mapper.Map<CourseDto>(context.Courses.SingleOrDefault(c => c.Id == id));

                // Throws exception if no matching course is found
                if (course == null)
                    throw new InvalidOperationException("No course with a matching id was found.");
            }

            return course;
        }

        /// <summary>
        /// Returns a Course with the given title.
        /// </summary>
        /// <param name="title">Course title.</param>
        /// <returns>CourseDto.</returns>
        public CourseDto FindByTitle(string title)
        {
            // Throw exception for invalid title value
            if (String.IsNullOrWhiteSpace(title))
                throw new ArgumentNullException("title",
                    "title does not accept a null or empty string as an argument.");

            CourseDto course;

            // Open entity connection
            using (var context = new CollegeSimEntities())
            {
                // Mapping retrieved entity to targeted return type
                course = Mapper.Map<CourseDto>(context.Courses.Single(c => c.Title == title));
            }

            return course;
        }

        /// <summary>
        /// Saves a new Course.
        /// </summary>
        /// <param name="newCourse">new Course to save.</param>
        public void Insert(CourseDto newCourse)
        {
            // Throw exception for null dto
            if (newCourse == null)
                throw new ArgumentNullException("newCourse",
                    "newCourse does not accept a null dto as an argument.");

            // Open entity connection
            using (var context = new CollegeSimEntities())
            {
                // Throws an exception if a course with the given subject/coursenumber already exists
                if (context.Courses.SingleOrDefault(c => c.Subject == newCourse.Subject && c.CourseNumber == newCourse.CourseNumber) != null)
                    throw new InvalidOperationException("A course with the given subject and course number already exists.");

                // Throws an exception if a course with the given title already exists
                if (context.Courses.SingleOrDefault(c => c.Title == newCourse.Title) != null)
                    throw new InvalidOperationException("A course with the given title already exists.");

                context.Courses.Add(Mapper.Map<Course>(newCourse));
                context.SaveChanges();
            }
        }

        /// <summary>
        /// Updates an existing Course.
        /// </summary>
        /// <param name="courseUpdate">Course to update.</param>
        public void Update(CourseDto courseUpdate)
        {
            // Throw exception for null dto
            if (courseUpdate == null)
                throw new ArgumentNullException("courseUpdate",
                    "courseUpdate does not accept a null dto as an argument.");

            // Open entity connection
            using (var context = new CollegeSimEntities())
            {
                // Look for cuorse to be updated
                var course = context.Courses.SingleOrDefault(c => c.Id == courseUpdate.Id);

                // Throws an exception if no matching entry is found
                if (course == null)
                    throw new InvalidOperationException("No entry matching the given course was found.");

                // Throws an exception if a course with the given subject/coursenumber already exists
                if (context.Courses.SingleOrDefault(c => c.Subject == courseUpdate.Subject && c.CourseNumber == courseUpdate.CourseNumber) != null && courseUpdate.Subject + courseUpdate.CourseNumber != course.Subject + course.CourseNumber)
                    throw new InvalidOperationException("Another course with the given subject and course number already exists.");

                // Throws an exception if a course with the given title already exists
                if (context.Courses.SingleOrDefault(c => c.Title == courseUpdate.Title) != null && courseUpdate.Title != course.Title)
                    throw new InvalidOperationException("Another course with the given title already exists.");

                // Update existing category
                course.CRN = courseUpdate.CRN;
                course.Subject = courseUpdate.Subject;
                course.CourseNumber = courseUpdate.CourseNumber;
                course.Title = courseUpdate.Title;
                course.CreditHours = courseUpdate.CreditHours;
                course.ContactHours = courseUpdate.ContactHours;
                course.InstructorId = courseUpdate.InstructorId;
                course.IsOnline = courseUpdate.IsOnline;

                context.SaveChanges();
            }
        }

        /// <summary>
        /// Deletes an existing Course.
        /// </summary>
        /// <param name="courseDel">Course to delete.</param>
        public void Delete(CourseDto courseDel)
        {
            // Throw exception for null dto
            if (courseDel == null)
                throw new ArgumentNullException("courseDel",
                    "courseDel does not accept a null dto as an argument.");

            // Open entity connection
            using (var context = new CollegeSimEntities())
            {
                // Look for course to be deleted
                var course = context.Courses.SingleOrDefault(c => c.Id == courseDel.Id);

                // Throws an exception if no matching entry is found
                if (course == null)
                    throw new InvalidOperationException("No entry matching the given course was found.");

                context.Courses.Remove(course);
                context.SaveChanges();
            }
        }

        #endregion
    }
}
